#include <iostream>
#include <cstddef>

void pointer_to_pointer_example() {
    int i = 5; // число

    int *i_ptr = &i; // указатель на число -> связанный тип int

    int * * i_ptr_ptr = &i_ptr; // указатель на указатель -> связанный тип int *

    std::cout << i << " " << i_ptr << " " << i_ptr_ptr << '\n';

    std::cout << *i_ptr << " " << *i_ptr_ptr << '\n';

    std::cout << *(*i_ptr_ptr) << '\n';

    int * * * i_ptr_ptr_ptr = &i_ptr_ptr;
}

void array_of_pointers() {
    int* *arr_of_ptr = new (std::nothrow) int*[3] {nullptr, nullptr, nullptr};

    if (nullptr != arr_of_ptr) {
        int i = 1, j = 2, k = 3;
        arr_of_ptr[0] = &i;
        arr_of_ptr[1] = &j;
        arr_of_ptr[2] = &k;

        std::cout << arr_of_ptr << std::endl;
        std::cout << arr_of_ptr[0] << " " << arr_of_ptr[1] << " " << arr_of_ptr[2] << std::endl;
        std::cout << *arr_of_ptr[0] << " " << *arr_of_ptr[1] << " " << *arr_of_ptr[2] << std::endl;
    }

    delete[] arr_of_ptr; 
}

void array_of_arrays() {
    int* *arr_of_arrs = new (std::nothrow) int*[3] {nullptr, nullptr, nullptr};

    if (nullptr != arr_of_arrs) {

        for (std::size_t idx = 0u; idx != 3u; ++idx)
            arr_of_arrs[idx] = new (std::nothrow) int[4];

        bool good = true;
        for (std::size_t idx = 0u; idx != 3u; ++idx)
            if (nullptr == arr_of_arrs[idx]) good = false;
        
        if (good) {
            int* sub_array = arr_of_arrs[0];
            sub_array[0] = 1;
            sub_array[1] = 2;
            sub_array[2] = 3;
            sub_array[3] = 4;

            // эквивалентно
            arr_of_arrs[0][0] = 1;
            arr_of_arrs[0][1] = 2;
            arr_of_arrs[0][2] = 3;
            arr_of_arrs[0][3] = 4;
        }
    }
    for (std::size_t idx = 0u; idx != 3u; ++idx)
        delete[] arr_of_arrs[idx];
    delete[] arr_of_arrs;
}

void ptr_to_ptr_const() {
    int i = 5;
    int *ptr = &i;
    int **ptr_ptr = &ptr;

    int const *cv_ptr = ptr; // good

    //int const * * cv_ptr_ptr = ptr_ptr; // bad

    int * const ptr_cv = ptr; // good
    int * * const ptr_ptr_cv = ptr_ptr; // good

    int const * const * cv_ptr_cv_ptr = ptr_ptr; // good

    /*{
        const int n = 42;
        const int* cp = &n;

        int* p;
        int** a = &p;

        const int** b;
        b = a;  // если бы это компилировалось
        *b = cp;  // то это присваивание, верное для b и для cp
        *p = 84;  // привело бы к возможности изменить константу через p
    }*/
}

int main() {
    if (false) pointer_to_pointer_example();
    if (false) array_of_pointers();
    if (false) array_of_arrays();
    if (true) ptr_to_ptr_const();
    return 0;
}